%name qtype_preg_
%include {
    global $CFG;
    require_once($CFG->dirroot . '/question/type/poasquestion/poasquestion_string.php');
    require_once($CFG->dirroot . '/question/type/preg/preg_nodes.php');
    require_once($CFG->dirroot . '/question/type/preg/treger/build_nodes.php');

}
%declare_class {class qtype_preg_parser}
%include_class {
    // Root of the Abstract Syntax Tree (AST).
    private $root;
    private $templates;

    public function __construct($templates=null) {
        $this->root = null;
        $this->templates = $templates;
    }

    public function get_root() {
        return $this->root;
    }

}



%left ALTERNATIVE.
%left CONCAT.
%left BEG ATEND FIND GROUP  IFM THEN NUMBER  TEMPLATE  CONSTANT  DIAPASON  BOUND NOTBOUND.
%left ANYOF ANYEXCEPTOF ONEOF ELSEM.
%right FROM TO NOGREED.
%left MAYBE.
%left LP RP LB RB.

start ::= expr(B). {
    // Set the root node.
    $this->root = B;
}

expr(A) ::= expr(B) CONCAT expr(C). {    
    if (B->type == qtype_preg_node::TYPE_NODE_CONCAT && C->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        B->operands = array_merge(B->operands, C->operands);
        A = B;
    } else if (B->type == qtype_preg_node::TYPE_NODE_CONCAT && C->type != qtype_preg_node::TYPE_NODE_CONCAT) {
        if(is_a(C,'qtype_preg_leaf_assert_circumflex')) {
            B->operands = array_merge(array(C), B->operands);
        }
        else {
            B->operands[] = C;
        }
        A = B;
    } else if (B->type != qtype_preg_node::TYPE_NODE_CONCAT && C->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        if(is_a(B,'qtype_preg_leaf_assert_dollar')) {
            C->operands[] = B;
        }
        else {
            C->operands = array_merge(array(B), C->operands);
        }
        A = C;
    } else {
        A = new qtype_preg_node_concat();
        if(is_a(C,'qtype_preg_leaf_assert_circumflex') || is_a(C,'qtype_preg_leaf_assert_dollar')) {
            A->operands[] = C;
            A->operands[] = B;
        }
        else {
            A->operands[] = B;
            A->operands[] = C;
        }
    }
    //A->set_user_info(B->position->compose(C->position));
}

expr(A) ::= expr(B) CONCAT. {
    A = B;
}

expr(A) ::= expr(B) expr(C).  [CONCAT] {
    if (B->type == qtype_preg_node::TYPE_NODE_CONCAT && C->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        B->operands = array_merge(B->operands, C->operands);
        A = B;
    } else if (B->type == qtype_preg_node::TYPE_NODE_CONCAT && C->type != qtype_preg_node::TYPE_NODE_CONCAT) {
        if(is_a(C,'qtype_preg_leaf_assert_circumflex')) {
            B->operands = array_merge(array(C), B->operands);
        }
        else {
            B->operands[] = C;
        }
        A = B;
    } else if (B->type != qtype_preg_node::TYPE_NODE_CONCAT && C->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        if(is_a(B,'qtype_preg_leaf_assert_dollar')) {
            C->operands[] = B;
        }
        else {
            C->operands = array_merge(array(B), C->operands);
        }
        A = C;
    } else {
        A = new qtype_preg_node_concat();
        if(is_a(C,'qtype_preg_leaf_assert_circumflex') || is_a(B,'qtype_preg_leaf_assert_dollar')) {
            A->operands[] = C;
            A->operands[] = B;
        }
        else {
            A->operands[] = B;
            A->operands[] = C;
        }
    }
    //A->set_user_info(B->position->compose(C->position));
}

expr(A) ::= expr(B) ALTERNATIVE(C) expr(D). {
    if (B->type == qtype_preg_node::TYPE_NODE_ALT && D->type == qtype_preg_node::TYPE_NODE_ALT) {
        B->operands = array_merge(B->operands, D->operands);
        A = B;
    } else if (B->type == qtype_preg_node::TYPE_NODE_ALT && D->type != qtype_preg_node::TYPE_NODE_ALT) {
        B->operands[] = D;
        A = B;
    } else if (B->type != qtype_preg_node::TYPE_NODE_ALT && D->type == qtype_preg_node::TYPE_NODE_ALT) {
        D->operands = array_merge(array(B), D->operands);
        A = D;
    } else {
        A = new qtype_preg_node_alt();
        A->operands[] = B;
        A->operands[] = D;
    }
    //A->set_user_info(B->position->compose(D->position), array(new qtype_preg_userinscription('|')));
}

expr(A) ::= ONEOF(B) expr(C). {
    A = C;
    //A->set_user_info(B->position->compose(C->position), array(new qtype_preg_userinscription('|')));    
}

expr(A) ::= LP expr(B) RP. {
    A = B;
}

expr(A) ::= expr(B) numrep(C). {
    A = C;
    A->operands[0] = B;
}

expr(A) ::= expr(B) numrep(C) NOGREED. {
    C->lazy = true;
    C->gredy = false;
    C->userinscription[0] .='?';
    A = C;
    A->operands[0] = B;
}

expr(A) ::= MAYBE expr(B). {
    A = form_quant('?', false, 0, 1, false, true, false);
    A->operands[0] = B;
}

expr(A) ::= GROUP CONSTANT(B) LB expr(C) RB. {
    A = form_named_subexpr('(?P<'.B.'>...)',B);
    A->operands[0] = C;
}

expr(A) ::= IFM CONSTANT(B) THEN expr(C) ELSEM expr(D). {
    A = form_numeric_or_named_cond_subexpr('(?('.B.')...|...)',B);
    A->operands[0] = C;
    A->operands[1] = D;
}

expr(A) ::= ANYOF expr(B). { 
    if (B->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        $diap = new qtype_poasquestion_string('[');
        $res = new qtype_poasquestion_string('');
        foreach(B->operands as $i => $child) {
            $data = $child->flags[0][0]->data->string();
            if($child->flags[0][0]->data->length()>1) {
                $data = qtype_preg_unicode::substr($data,1,3);
                $data = expand_charset_range($data);
            }   
            $res->concatenate($data);
            $df = $res->string();
        }

        $res = qtype_preg_unicode::get_ranges_from_charset($res);

        foreach($res as $key => $value) {
            if($value[0] == $value[1])
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[0]));
            else {
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[0]));
                $diap->concatenate("-");
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[1]));   
            }
        }
        $diap->concatenate(']');
        A = form_charset($diap->string(),qtype_preg_charset_flag::TYPE_SET,$diap->string());
    }
    else {
        A= B;
    }
}

expr(A) ::= ANYEXCEPTOF expr(B). {
    if (B->type == qtype_preg_node::TYPE_NODE_CONCAT) {
        $diap = new qtype_poasquestion_string('[^');
        $res = new qtype_poasquestion_string('');
        foreach(B->operands as $i => $child) {
            $data = $child->flags[0][0]->data->string();
            if($child->flags[0][0]->data->length()>1) {
                $data = qtype_preg_unicode::substr($data,1,3);
                $data = expand_charset_range($data);
            }   
            $res->concatenate($data);
            $df = $res->string();
        }

        $res = qtype_preg_unicode::get_ranges_from_charset($res);

        foreach($res as $key => $value) {
            if($value[0] == $value[1])
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[0]));
            else {
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[0]));
                $diap->concatenate("-");
                $diap->concatenate(qtype_preg_unicode::code2utf8($value[1]));   
            }
        }
        $diap->concatenate(']');
        A = form_charset($diap->string(),qtype_preg_charset_flag::TYPE_SET,$diap->string());
    }
    else {
        A= B;
    }
}

expr(A) ::= FIND CONSTANT(B). {
    A = form_named_backref('\k<'.B.'>', 3, '<', '>');
}

expr(A) ::= CONSTANT(B).    {
    if(core_text::strlen(B)>1) {
        A = new qtype_preg_node_concat();
        A->operands = string_to_tokens(B);
    }
    else {
        A = string_to_tokens(B)[0];
    }
}

expr(A) ::= TEMPLATE(B). {
    if(array_key_exists(B,$this->templates)) {
        A = convert_Template($this->templates[B]);
    }
    else {
        A = new qtype_preg_node_error(qtype_preg_node_error::SUBTYPE_UNKNOWN_ERROR, 'template not exist');
    }

}

expr(A) ::= DIAPASON(B). {
    A = form_charset('['.B.']', qtype_preg_charset_flag::TYPE_SET,'['.B.']');
}

expr(A) ::= BEG.{
    A = form_simple_assertion('^','qtype_preg_leaf_assert_circumflex');
}

expr(A) ::= ATEND. {
    A = form_simple_assertion('$','qtype_preg_leaf_assert_dollar');
}

expr(A) ::= BOUND. {
    A = form_simple_assertion('\b','qtype_preg_leaf_assert_esc_b');
}

expr(A) ::= NOTBOUND. {
    A = form_simple_assertion('\B','qtype_preg_leaf_assert_esc_b',true);
}

numrep(A) ::= FROM(B) TO(C). {
    A = form_quant('{'.B.','.C.'}', false, B, C, false, true, false);
}

numrep(A) ::= FROM(B). {
    A = form_quant('{'.B.',}', true, B, -1, false, true, false);
}

numrep(A) ::= TO(B). {
    A = form_quant('{,'.B.'}', false, 0, B, false, true, false);
}

numrep(A) ::= NUMBER(C). {
    A = form_quant('{'.C.'}', false, C, C, false, true, false);
}